package com.ddw.meedeeuh.shared.movie.scrapers.local;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;

import com.ddw.meedeeuh.shared.io.PathUtils;
import com.ddw.meedeeuh.shared.io.SourceFile;
import com.ddw.meedeeuh.shared.io.SourceFileFactory;
import com.ddw.meedeeuh.shared.io.SourceFolder;
import com.ddw.meedeeuh.shared.movie.EditableMovie;
import com.ddw.meedeeuh.shared.movie.MovieSettings;
import com.ddw.meedeeuh.shared.movie.scrapers.MovieScraper;
import com.ddw.meedeeuh.shared.movie.scrapers.MovieScraperResult;
import com.ddw.meedeeuh.shared.movie.scrapers.MovieScraperResultFactory;
import com.ddw.meedeeuh.shared.movie.scrapers.MovieScraperResultType;

class LocalMovieScraper implements MovieScraper {

	private static final Logger LOGGER = Logger.getLogger(LocalMovieScraper.class.getName());
	private static SAXParser PARSER = null;
	private static final Comparator<File> FILE_NAME_COMPARATOR = new Comparator<File>() {

		@Override
		public int compare(File o1, File o2) {
			return o1.getName().compareToIgnoreCase(o2.getName());
		}

	};
	private final List<File> cachedMovieFolderDirectories;
	private final List<File> cachedMovieFolderFiles;

	public LocalMovieScraper(List<File> cachedMovieFolderDirectories, List<File> cachedMovieFolderFiles) {
		this.cachedMovieFolderDirectories = cachedMovieFolderDirectories;
		this.cachedMovieFolderFiles = cachedMovieFolderFiles;
	}

	private static SAXParser getSaxParser() throws ParserConfigurationException, SAXException {
		if(PARSER == null) {
			final SAXParserFactory factory = SAXParserFactory.newInstance();
			PARSER = factory.newSAXParser();
		} else {
			PARSER.reset();
		}
		return PARSER;
	}

	@Override
	public MovieScraperResult scrape(MovieSettings settings, SourceFolder movieFolder, List<SourceFile> mediaFiles) {
		LOGGER.finer("Entering the local movie scraper routine");

		LOGGER.finest("Extracting movie name");
		final String movieName = getMovieName(settings, movieFolder, mediaFiles);
		if(movieName == null) {
			LOGGER.finer("Movie name not found");
			return MovieScraperResultFactory.create(MovieScraperResultType.FAILED_PARSE);
		}

		final EditableMovie movie = LocalMovieFactory.create(movieFolder, movieName);

		if(settings.usePosters()) {
			LOGGER.finest("Searching for poster");
			final File poster = searchForPoster(settings, movieFolder, mediaFiles);
			if(poster == null) {
				LOGGER.finer("No poster found");
			} else {
				LOGGER.finer("Poster found");
				movie.setPoster(SourceFileFactory.create(poster));
			}
		}

		if(settings.useFanart()) {
			LOGGER.finest("Searching for fanart");
			final File fanart = searchForFanart(settings, movieFolder, mediaFiles);
			if(fanart == null) {
				LOGGER.finer("No fanart found");
			} else {
				LOGGER.finer("Fanart found");
				movie.setFanart(SourceFileFactory.create(fanart));
			}
		}

		if(settings.useExtraThumbs()) {
			LOGGER.finest("Searching for extra thumbs");
			final File[] extraThumbs = searchForExtraThumbs(settings, movieFolder, mediaFiles);
			if(extraThumbs == null || extraThumbs.length == 0) {
				LOGGER.finer("No extra thumbs found");
			} else {
				LOGGER.finer(String.format("%d Extra thumbs found", extraThumbs.length));
				for(int i = 0, s = extraThumbs.length; i < s; i++) {
					movie.addExtraThumb(LocalExtraThumbFactory.create(extraThumbs[i]));
				}
			}
		}

		LOGGER.finest("Searching for nfo");
		final File nfo = searchForNfo(settings, movieFolder, mediaFiles);
		if(nfo == null) {
			LOGGER.finer("No nfo found");
			return MovieScraperResultFactory.create(MovieScraperResultType.SUCCESS, movieFolder, movie);
		} else {
			LOGGER.finer("Nfo found");
			movie.setNfo(SourceFileFactory.create(nfo));
		}

		LOGGER.finer("Getting xml parser");
		SAXParser saxParser = null;
		try {
			saxParser = getSaxParser();
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, "Failed to create xml parser", e);
		}

		// Check the parser is valid
		if(saxParser == null) {
			LOGGER.finer("Cannot create xml parser, using basic movie info");
			return MovieScraperResultFactory.create(MovieScraperResultType.SUCCESS, movieFolder, movie);
		}

		// Reset the name to null as it may be parsed
		LOGGER.finest("Temporarily setting movie name to null, waiting for nfo parser to find movie title");
		movie.setName(null);

		LOGGER.finer("Attempting to parse nfo");
		try {
			saxParser.parse(nfo, new MovieNfoHandler(movie, settings, LOGGER));
			LOGGER.finer("Xml parser finished successfully");
		} catch (SAXException e) {
			LOGGER.log(Level.SEVERE, "Xml parser failed", e);
		} catch (IOException e) {
			LOGGER.log(Level.SEVERE, "Xml parser failed, error reading nfo", e);
		}

		// Reset the movie name if it wasn't found
		if(movie.getName() == null) {
			LOGGER.finest("Nfo parser failed to find movie title, resetting previous movie name");
			movie.setName(movieName);
		}

		LOGGER.finer("Exiting the local movie scraper routine");
		return MovieScraperResultFactory.create(MovieScraperResultType.SUCCESS, movieFolder, movie);
	}

	private File[] searchForExtraThumbs(final MovieSettings settings, SourceFolder movieFolder, List<SourceFile> mediaFiles) {
		// First search for extra thumbs folder
		final File potentialExtraThumbsFolder = new File(PathUtils.combine(movieFolder.getInstance(), settings.getExtraThumbsFolderName()));
		final boolean exists = movieFolderDirectoryExists(potentialExtraThumbsFolder);
		if(exists == false) {
			return null;
		}

		// If we have a directory then search for thumbs in it
		return potentialExtraThumbsFolder.listFiles(new FileFilter() {

			@Override
			public boolean accept(File child) {
				final Matcher matcher = settings.getExtraThumbsPattern().matcher(child.getName());
				return matcher.find() && child.isFile();
			}

		});
	}

	private File searchForFanart(MovieSettings settings, SourceFolder movieFolder, List<SourceFile> mediaFiles) {
		final File potentialFanart = new File(PathUtils.combine(movieFolder.getInstance(), settings.getFanartName()));
		return movieFolderFileExists(potentialFanart) ? potentialFanart : null;
	}

	private File searchForPoster(MovieSettings settings, SourceFolder movieFolder, List<SourceFile> mediaFiles) {
		final File potentialPoster = new File(PathUtils.combine(movieFolder.getInstance(), settings.getPosterName()));
		return movieFolderFileExists(potentialPoster) ? potentialPoster : null;
	}

	private String getMovieName(MovieSettings settings, SourceFolder movieFolder, List<SourceFile> mediaFiles) {
		if(settings.matchNameOnParentFolder()) {
			LOGGER.finest("Extracting movie name from parent folder");
			final Matcher matcher = settings.getNameRegex().matcher(movieFolder.getInstance().getName());
			if(matcher.find()) {
				LOGGER.finer(String.format("Movie name: %s matched on parent folder: %s", matcher.group(), movieFolder.getInstance().getName()));
				return matcher.group();
			}
		} else {
			LOGGER.finest("Extracting movie name from media file(s)");
			for(final SourceFile mediaFile : mediaFiles) {
				final Matcher matcher = settings.getNameRegex().matcher(mediaFile.getInstance().getName());
				if(matcher.find()) {
					LOGGER.finer(String.format("Found movie name: %s on media file: %s", matcher.group(), mediaFile.getInstance().getName()));
					return matcher.group();
				}
				LOGGER.finest(String.format("No movie name found on media file: %s", mediaFile.getInstance().getName()));
			}
		}

		return null;
	}

	private File searchForNfo(MovieSettings settings, SourceFolder movieFolder, List<SourceFile> mediaFiles) {
		// Check each media file first for nfo
		if(settings.matchNameOnNfo()) {
			for(final SourceFile mediaFile : mediaFiles) {
				// Check potential nfo exists
				final File mediaFileNfo = new File(PathUtils.changeExtension(mediaFile.getInstance(), ".nfo"));
				final boolean exists = movieFolderFileExists(mediaFileNfo);
				if(exists) {
					return mediaFileNfo;
				}
			}
		}

		// Fallback to using movie.nfo
		final File potentialNfo = new File(PathUtils.combine(movieFolder.getInstance(), "movie.nfo"));
		return movieFolderFileExists(potentialNfo) ? potentialNfo : null;
	}

	private final boolean movieFolderFileExists(File file) {
		return Collections.binarySearch(this.cachedMovieFolderFiles, file, FILE_NAME_COMPARATOR) >= 0;
	}

	private final boolean movieFolderDirectoryExists(File directory) {
		return Collections.binarySearch(this.cachedMovieFolderDirectories, directory, FILE_NAME_COMPARATOR) >= 0;
	}

}
